/*
 * Diego da Rocha Borges
 * Otávio Zabaleta
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "symbol_table.h"
#include "ast.h"

void hash_init(HASH_TABLE* table)
{
    int x = 0;
    for (x = 0; x < TABLE_SIZE; x++) {
        table->entries[x] = NULL;
    }
}

int hash_address(char *lexeme)
{
    int i = 0;
    int address = 1;
    for (i = 0; i < strlen(lexeme); i++) {
        address = ((address * lexeme[i]) % TABLE_SIZE) + 1;
    }
    return address;
}

/**
 * inserts the entry node into the table address.
 *
 * <p>
 * In case that the address already has an entry, then, the new element will be
 * added to the begining of the linked list of entries for that address.
 * </p>
 *
 * @param table the symbol table
 * @param entry_node the new entry node to be inserted into the symbol table.
 */
void insert_entry_node(HASH_TABLE* table, HASH_NODE* entry_node)
{
    int address = hash_address(entry_node->data->lexeme);

    if(table->entries[address] == NULL) {
        //printf("[adding] lexeme: %s, address: %d\n", entry_node->data->lexeme, address);
        table->entries[address] = entry_node;
    } else {
        //printf("[linking] lexeme: %s, address: %d\n", entry_node->data->lexeme, address);
        entry_node->next = table->entries[address];
        table->entries[address] = entry_node;
    }
}

HASH_NODE* hash_insert_entry(HASH_TABLE* table, char* lexeme, int type, int line_num)
{
    HASH_NODE* entry_node = NULL;
    
    /*
     * if the lexeme already exists in the table, a pointer to the entry node is
     * returned.
     */
    entry_node = hash_lookup(*table, lexeme, type);
    if (entry_node != NULL) {
        //printf("lexeme %s already exists!\n", lexeme);
        return entry_node;
    }
    /*
     * At this point, the lexeme was not found in the table, so a new entry node
     * is created to insert the lexeme into the table.
     */
    entry_node = hash_create_hash_node(lexeme, type, line_num);
    insert_entry_node(table, entry_node);

    return entry_node;
}

HASH_NODE* hash_lookup(HASH_TABLE table, char* lexeme, int type) {
    int address = hash_address(lexeme);
    HASH_NODE* current_node = table.entries[address];

    if(current_node != NULL) {

        //printf("lexeme: %s, type: %d\n", lexeme, type);
        //printf("node lexeme: %s, type: %d\n", current_node->data->lexeme, current_node->data->type);
        while(current_node != NULL) {
            if ((strcmp(lexeme, current_node->data->lexeme) == 0) &&
                (type == current_node->data->type)) {

                return current_node;
            }
            current_node = current_node->next;
        }
        return current_node;

    } else {
        return NULL;
    }
}

HASH_NODE* hash_create_hash_node(char* lexeme, int type, int line_num)
{
    HASH_ENTRY* entry = malloc(sizeof(HASH_ENTRY));
    char* lexemeCopy = malloc(strlen(lexeme) * sizeof(char));
    strcpy(lexemeCopy, lexeme);
    entry->lexeme = lexemeCopy;
    entry->type = type;
    entry->line_num = line_num;

    HASH_NODE* entry_node = malloc(sizeof(HASH_NODE));
    entry_node->data = entry;
    entry_node->next = NULL;
    return entry_node;
}

void hash_print(HASH_TABLE table)
{
    int address = 1;
    int i;
  
    printf("####### Symbol Table #######\n");
    for (i = 0; i < TABLE_SIZE; i++) {
        HASH_NODE* current_node = table.entries[i];
        while(current_node != NULL) {
            printf("[@%d]: type: %d, lexeme: %s, dataType: %d, line num: %d\n", i, current_node->data->type, current_node->data->lexeme, current_node->data->dataType, current_node->data->line_num);
            current_node = current_node->next;
        }
    }
    printf("############################\n");
}


char* hashBuffer;
int result_var_count = 0;
HASH_NODE* make_result_var(int dataType)
{
    hashBuffer = calloc(1, sizeof(char));
    sprintf(hashBuffer, "result_var_%d", result_var_count++);
    HASH_NODE* node = hash_insert_entry(table, hashBuffer, SYMBOL_VAR_DEC, 0);
    node->data->dataType = dataType;
    return node;
}

int label_count = 0;
HASH_NODE* make_label(void)
{
    hashBuffer = calloc(1, sizeof(char));
    sprintf(hashBuffer, "label_%d", label_count++);
    return hash_insert_entry(table, hashBuffer, SYMBOL_IDENTIFIER, 0);
}